﻿using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.OpenGL;
using OpenTK.Input;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LDLib
{
    /// <summary>
    /// Game engine class. Handles updating the game logic,
    /// rendering the objects and input from the user.
    /// </summary>
    public class Engine : GameWindow
    {
        /// <summary>
        /// A random number generator, initialize at start-up.
        /// </summary>
        public static Random Random { get; private set; }

        /// <summary>
        /// Current scene
        /// </summary>
        public static Scene CurrentScene { get; set; }

        /// <summary>
        /// GUI
        /// </summary>
        public static GUI GUI { get; private set; }

        /// <summary>
        /// Constructs a new engine with basic settings
        /// </summary>
        public Engine(string title) : base(1280, 720, GraphicsMode.Default, title)
        {
            Engine.Random = new Random();
        }

        /// <summary>
        /// Loading function. This is where the game is
        /// set up and the resources are loaded.
        /// </summary>
        /// <param name="e"></param>
        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            R.LoadCoreResources();
            new Camera();
            GUI = new GUI();

            // Subscribe the input delegates
            Keyboard.KeyDown += Keyboard_KeyDown;
            Keyboard.KeyUp += Keyboard_KeyUp;
            Mouse.ButtonDown += Mouse_ButtonDown;
            Mouse.ButtonUp += Mouse_ButtonUp;
            Mouse.Move += Mouse_Move;
        }

        /// <summary>
        /// Mouse movement delegate. Gets called
        /// when the mouse is moved.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Mouse_Move(object sender, MouseMoveEventArgs e)
        {
            Input.mouseX = e.X;
            Input.mouseY = e.Y;
            Input.mouseDeltaX = e.XDelta;
            Input.mouseDeltaY = e.YDelta;
        }

        /// <summary>
        /// Mouse 'button up' delegate.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Mouse_ButtonUp(object sender, MouseButtonEventArgs e)
        {
            Input.mouseUp.Add(e.Button);
            Input.mousePressed.Remove(e.Button);
        }

        /// <summary>
        /// Mouse 'button down' delegate.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Mouse_ButtonDown(object sender, MouseButtonEventArgs e)
        {
            Input.mouseDown.Add(e.Button);
            Input.mousePressed.Add(e.Button);
        }

        /// <summary>
        /// Keyboard 'key up' delegate.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Keyboard_KeyUp(object sender, KeyboardKeyEventArgs e)
        {
            Input.keysPressed.Remove(e.Key);
            Input.keysUp.Add(e.Key);
        }

        /// <summary>
        /// Keyboard 'key down' delegate.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Keyboard_KeyDown(object sender, KeyboardKeyEventArgs e)
        {
            Input.keysPressed.Add(e.Key);
            Input.keysDown.Add(e.Key);
        }

        /// <summary>
        /// Update frame function. Gets called every frame.
        /// </summary>
        /// <param name="e"></param>
        protected sealed override void OnUpdateFrame(FrameEventArgs e)
        {
            Time.time += e.Time;
            Time.delta = e.Time;

            base.OnUpdateFrame(e);
            if (CurrentScene != null) CurrentScene.Update(e.Time);
            OnUpdate(e.Time);
            GUI.Update(e.Time);
        }

        /// <summary>
        /// Update frame function. Override this for your 
        /// game logic needs. :)
        /// </summary>
        /// <param name="dt">Time (in seconds) since the last frame</param>
        protected virtual void OnUpdate(double dt)
        {
            // Do nothing here, override instead!
        }

        /// <summary>
        /// Render frame function. Renders the objects.
        /// </summary>
        /// <param name="e"></param>
        protected sealed override void OnRenderFrame(FrameEventArgs e)
        {
            base.OnRenderFrame(e);

            GL.ClearColor(0f, 0f, 0f, 0f);
            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);

            // Call the "pre render" function
            OnPreRender(e.Time);

            // Do the actual rendering
            if (CurrentScene != null) CurrentScene.Render(e.Time);

            // Render the GUI
            GUI.Render();

            // Call the "post render" function
            OnPostRender(e.Time);

            SwapBuffers();
        }

        /// <summary>
        /// This function gets called BEFORE the engine does
        /// the actual rendering. Here you can do some "pre-render" stuff,
        /// if you need to.
        /// </summary>
        /// <param name="dt">Time (in seconds) since the last frame</param>
        protected virtual void OnPreRender(double dt)
        {
            // Do nothing here, override instead!
        }

        /// <summary>
        /// This function gets called AFTER the engine has
        /// done the actual rendering. Here you can do some "post-render" stuff,
        /// if you need to (like post-process effects etc.).
        /// </summary>
        /// <param name="dt"></param>
        protected virtual void OnPostRender(double dt)
        {
            // Do nothing here, override instead!
        }
    }
}
